package ${projectDomain}.database;

import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.unswift.annotation.api.Api;
import com.unswift.annotation.api.ApiField;
import com.unswift.annotation.api.ApiMethod;
import com.unswift.cache.MemoryCache;
import ${projectDomain}.cache.CacheEnum;
import ${projectDomain}.constant.ApplicationContant;
import ${projectDomain}.sql.SqlInvocationHandler;
import ${projectDomain}.utils.BeanUtils;
import com.unswift.enums.ExceptionEnum;
import com.unswift.exception.CoreException;
import com.unswift.utils.ClassUtils;
import com.unswift.utils.ExceptionUtils;
import com.unswift.utils.ObjectUtils;
import com.unswift.utils.StringUtils;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
@DependsOn("beanUtils")
@Api(value="数据源配置", author="unswift", date="2023-04-08", version="1.0.0")
public class DataSourceConfig implements BeanFactoryAware, BeanDefinitionRegistryPostProcessor, EnvironmentAware{

	@ApiField("日志对象")
	protected final Logger logger=LoggerFactory.getLogger(this.getClass());
	
	@ApiField("spring bean 工厂")
	private BeanFactory beanFactory;
	
	@ApiField("spring 环境，可以获取配置文件信息")
	private Environment environment;
	
	@Override
	@SuppressWarnings("unchecked")
	@ApiMethod(value="数据源配置初始化方法", params = @ApiField("bean注册对象"))
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		ApplicationContant.loadExceptionMessage();
		DataSourceProperties unswiftProperties=null;
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		try {
			BindResult<DataSourceProperties> restServiceBindResult = Binder.get(environment).bind("unswift", DataSourceProperties.class);
			unswiftProperties = restServiceBindResult.get();
			String datasourceType;
			BeanDefinitionBuilder builder;
			Map<String, Object> datasourceConfig;
			AbstractBeanDefinition dataSourceBeanDefinition, sessionFactoryBeanDefinition;
			SqlSessionFactoryBean sessionFactoryBean;
			SqlSessionFactory sqlSessionFactory;
			for (Map.Entry<String, Object> entry : unswiftProperties.getDatasource().entrySet()) {
				datasourceType=entry.getKey();
				datasourceConfig=(Map<String, Object>)entry.getValue();
				builder = BeanDefinitionBuilder.genericBeanDefinition(HikariDataSource.class);
				for (Map.Entry<String, Object> datasourceParam : datasourceConfig.entrySet()) {
					if(datasourceParam.getKey().equals("mapper-locations") || datasourceParam.getKey().equals("base-package") || datasourceParam.getKey().equals("annotation-class")){
						continue;
					}
					builder.addPropertyValue(StringUtils.underlineToHump(datasourceParam.getKey()),datasourceParam.getValue());
				}
				dataSourceBeanDefinition = builder.getBeanDefinition();
				registry.registerBeanDefinition(datasourceType+"DataSource", dataSourceBeanDefinition);
				sessionFactoryBean = new SqlSessionFactoryBean();
				sessionFactoryBean.setDataSource((HikariDataSource)beanFactory.getBean(datasourceType+"DataSource"));
				Map<String, String> mapperLocations = (Map<String, String>)datasourceConfig.get("mapper-locations");
				List<Resource> resourceList=new ArrayList<Resource>();
				Resource[] resources;
				for (int i = 0, length=mapperLocations.size(); i < length; i++) {
					resources = resolver.getResources( mapperLocations.get(i+""));
					if(ObjectUtils.isEmpty(resources)) {
						continue ;
					}
					resourceList.addAll(ObjectUtils.asList(resources));
				}
				if(ObjectUtils.isNotEmpty(resourceList)) {
					sessionFactoryBean.setMapperLocations(resourceList.toArray(new Resource[resourceList.size()]));
				}
				sqlSessionFactory = sessionFactoryBean.getObject();
				builder = BeanDefinitionBuilder.genericBeanDefinition(sqlSessionFactory.getClass());
				builder.addConstructorArgValue(sqlSessionFactory.getConfiguration());
				sessionFactoryBeanDefinition=builder.getBeanDefinition();
				registry.registerBeanDefinition(datasourceType+"SqlSessionFactory", sessionFactoryBeanDefinition);
				builder = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionTemplate.class);
				builder.addConstructorArgValue(beanFactory.getBean(datasourceType+"SqlSessionFactory"));
				registry.registerBeanDefinition(datasourceType+"SqlSessionTemplate", builder.getBeanDefinition());
				builder = BeanDefinitionBuilder.genericBeanDefinition(DataSourceTransactionManager.class);
				builder.addConstructorArgValue(beanFactory.getBean(datasourceType+"DataSource"));
				registry.registerBeanDefinition(datasourceType+"TransactionManager", builder.getBeanDefinition());
				builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
				builder.addPropertyValue("sqlSessionFactoryBeanName", datasourceType+"SqlSessionFactory");
				builder.addPropertyValue("basePackage", datasourceConfig.get("base-package"));
				registry.registerBeanDefinition(datasourceType+"MapperScannerConfigurer", builder.getBeanDefinition());
				logger.info("加载数据源："+datasourceType);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw ExceptionUtils.exception("loading.dataSource.exception", e, e.getMessage());
		}
		try {
			//自定义sql加载
			Map<String, Object> customizeSql = unswiftProperties.getCustomizeSql();
			if(ObjectUtils.isNotEmpty(customizeSql)) {
				Map<String, String> sqlLocations=(Map<String, String>)customizeSql.get("sql-locations");
				if(ObjectUtils.isNotEmpty(sqlLocations)) {
					Resource[] resources;
					for (int i = 0, length=sqlLocations.size(); i < length; i++) {
						resources = resolver.getResources(sqlLocations.get(i+""));
						if(ObjectUtils.isEmpty(resources)) {
							continue;
						}
						for (Resource resource : resources) {
							parseSqlFile(registry, resource.getInputStream(), resource.getURL().getFile());
						}
					}
				}
			}
		} catch(CoreException e) {
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
			throw ExceptionUtils.exception("loading.customize.sql.exception", e, e.getMessage());
		}
	}
	
	@ApiMethod(value="解析sql文件内容", params = {@ApiField("spring Bean 容器"), @ApiField("sql文件输入流"), @ApiField("加载sql文件名称")})
	private void parseSqlFile(BeanDefinitionRegistry registry, InputStream stream, String fileName) {
		try {
			if(MemoryCache.getInstance().existsHash(CacheEnum.SQL_FILE_LOADDING.getKey(), fileName)) {//初始化了
				return ;
			}
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder xmlBuilder = factory.newDocumentBuilder();
			Document document = xmlBuilder.parse(stream);//加载异常XML文件
			Node namespaceNode = document.getDocumentElement().getAttributes().getNamedItem("namespace");
			ExceptionUtils.empty(namespaceNode, "file.field.empty", "mapper节点的namespace属性", fileName);
			String namespace=namespaceNode.getNodeValue();
			ExceptionUtils.empty(namespaceNode, "file.field.empty", "mapper节点的namespace属性", fileName);
			Class<?> sqlClass=ClassUtils.forName(namespace);
			Map<String, String> sqlMapper=new HashMap<String, String>();
			NodeList nodeList=document.getElementsByTagName("sql");//获取到异常映射关系节点
			if(ObjectUtils.isNotEmpty(nodeList)) {
				int length=nodeList.getLength();
				Node node;
				String sqlId, sqlContent, nodeName;
				for (int i = 0; i < length; i++) {
					node=nodeList.item(i);
					nodeName=StringUtils.toUpper(node.getNodeName());
					if("#TEXT".equals(nodeName)){
						continue;
					}
					Node idNode = node.getAttributes().getNamedItem("id");
					ExceptionUtils.empty(idNode, "file.field.empty", "sql节点的id属性", fileName);
					sqlId=idNode.getNodeValue();
					sqlContent=node.getTextContent();
					sqlMapper.put(sqlId, sqlContent);
				}
			}
			BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Proxy.class);
			builder.setFactoryMethod("newProxyInstance");
			builder.addConstructorArgValue(sqlClass.getClassLoader());
			builder.addConstructorArgValue(sqlClass);
			builder.addConstructorArgValue(new SqlInvocationHandler(sqlMapper));
			String beanName = StringUtils.toFirstLower(sqlClass.getSimpleName());
			registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
			BeanUtils.getAutowireCapableBeanFactory().autowireBean(beanFactory.getBean(beanName));
			MemoryCache.getInstance().setHash(CacheEnum.SQL_FILE_LOADDING.getKey(), fileName, true);
		} catch (CoreException e){
			throw e;
		} catch (Exception e) {
			e.printStackTrace();
			throw new CoreException(ExceptionEnum.PARSE_MESSAGE_XML.getCode(), String.format(ExceptionEnum.PARSE_MESSAGE_XML.getMessage(), e.getMessage()), e);
		}
	}
	
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		
	}

	public void setBeanFactory(BeanFactory beanFactory) {
		this.beanFactory = beanFactory;
	}
	
	@Override
	public void setEnvironment(Environment environment) {
		this.environment=environment;
	}
}
